(function (line) {
    line.define('line.ObservableObject', null, {
        events:['propertyChanged'],
        properties:{
            model:{
                defaultValue:null,
                getter:function (owner) {
                    return owner.getModel();
                },
                setter:function (owner, value) {
                    owner.setModel(value);
                }
            }
        },
        methods:{
            init:function () {
                this._bindings = {};
                this._reversedBindings = {};
                this._model = null;
                this.on('propertyChanged', this._onMyPropertyChanged, this);
            },
            setBinding:function (prop, path, source, oneway) {
                var propMember = this.getMember(prop, 'property');
                if (propMember && !propMember.readonly) {
                    var binding = this._bindings[path] = this._bindings[path] || [];
                    binding.push({
                        prop:prop,
                        source:source
                    });

                    if (!oneway) {
                        this._reversedBindings[prop] = {
                            path:path,
                            source:source
                        };
                    }

                    if (source && source instanceof line.ObservableObject) {
                        source.on('propertyChanged', this._onSourcePropertyChanged, this);
                        this.setProperty(prop, source.getProperty(path));
                    }
                }
                else {
                    console.log('Cannot bind property "' + prop + '", the property is either readonly or not exist.');
                }
            },
            getModel:function () {
                return this._model;
            },
            setModel:function (model) {
                var oldModel = this._model;
                if (oldModel) {
                    if (oldModel instanceof line.ObservableObject) {
                        oldModel.off('propertyChanged', this._onSourcePropertyChanged, this);
                    }
                }

                if (model) {
                    this._model = model;

                    if (model instanceof line.ObservableObject) {
                        model.on('propertyChanged', this._onSourcePropertyChanged, this);
                    }
                }

                this.evaluateBindings();
            },
            setProperty:function (name, value) {
                var oldValue = this.getProperty(name);
                if (oldValue !== value) {
                    this.inherited(name, value);
                    this.notifyPropertyChanged(name, value);
                }
            },
            evaluateBindings:function () {
                var model = this.getModel();
                var source, value;
                line.each(this._reversedBindings, function (binding, prop) {
                    source = binding.source || model;
                    value = source.getProperty(binding.path);
                    this.setProperty(prop, value);
                }, this);
            },
            notifyPropertyChanged:function (name, value) {
                this.fire('propertyChanged', {
                    name:name,
                    value:line.isUndefined(value) ? this.getProperty(name) : value
                });
            },
            _onMyPropertyChanged:function (sender, data) {
                var prop = data.name;
                var value = data.value;
                var binding = this._reversedBindings[prop];
                var source;
                if (binding) {
                    source = binding.source || this.getModel();
                    source.setProperty(binding.path, value);
                }
            },
            _onSourcePropertyChanged:function (sender, data) {
                var path = data.name;
                var value = data.value;
                var binding;

                if (path) {
                    binding = this._bindings[path];
                    if (binding) {
                        line.each(binding, function (b) {
                            this.setProperty(b.prop, value);
                        }, this);
                    }
                }
                else {
                    this.evaluateBindings();
                }
            }
        }
    });
})(line);
